#include "utilities.h"
#include <openssl/sha.h> /* please read this */
#ifndef min
#define min(A,B) (((A)>(B)) ? (B) : (A))
#endif /* ~min */

int printFlag = 0;
int signalIntFlag = 0;

pthread_t acceptThread;
pthread_t sig_thr_id;
pthread_t eventDisp;
pthread_t keepAliveThread;
pthread_t commandLine;
pthread_t timerThr;
pthread_mutex_t notifyLock;
int notifyCount = 0;
bool checkFlag = false;
bool minNeighFlag = false;
int globalAcceptSock = -1;

pthread_mutex_t statusRespLock;

int minNeighCount = 0;
bool notifyFlag = false;
bool joinFlag = false;
double globalStatusTime = -1;
double checkResponseTime = -1;
int joinCount = 0;
list<fromUOIDStr> myFromUOIDList;
list<uoidStr> myUOIDList;
map<int,beaconmap> myMap;
map<int, allTimers> myTimers;
unsigned int port_recieve = 0;
unsigned int port_timetolive = 0;
char port_recieve_str[512];
char hostname[512];
char node_id[512];
char node_instance_id[512];
char statusFile[128];
char input_line[512];
hello_msg myHello;
pthread_mutex_t maplock;
pthread_mutex_t timerlock;
pthread_mutex_t uoidlock;
pthread_mutex_t loglock;

multimap<int, int> statusResp;
map<string, Values> initMap;
map<int,bool> joinRequestMap;
list<statusSTR> statusResponseList;
bool isBeacon = false;
bool resetFlag = false;
list<beacon> allNeighbors;
list<beacon>::iterator itAllNeighbors;

//dispatcher heart queue
list<listBuffStruct> eventQueue;
pthread_mutex_t eventQueueMutex;

int nodeIndex = 0;

map<int, writeListData> writeMap;
pthread_mutex_t writeMapMutex;

multimap<int, neighborStruct> neighborMap;
multimap<int, neighborStruct>::iterator itNeighborMap;

bool dieChild = false;
bool serverShutdown = false;

int findInUOIDList(char* msgUOID)
{
	list<uoidStr>::iterator it;
	uoidStr tempUOIDStr;
	int n = -1;
	pthread_mutex_lock(&uoidlock);
	for ( it=myUOIDList.begin() ; it != myUOIDList.end(); it++ )
	{
		tempUOIDStr = *it;
		n = memcmp(msgUOID,tempUOIDStr.msgUOID,20);
		if(n==0)
		{
			pthread_mutex_unlock(&uoidlock);
			return 1;
		}
	} 
	pthread_mutex_unlock(&uoidlock);
	return 0;
}

void write_to_log(listBuffStruct lbtemp,int logtype,struct timeval exectime)
{
	FILE * pFile;
	char filepath[512];
	char message[512];
	char data[512];
	memset(message,'\0',512);
	memset(data,'\0',512);
	strcpy(filepath,initMap["HomeDir"].asStr);
	strcat(filepath,"/");
	strcat(filepath,initMap["LogFilename"].asStr);
	pFile = fopen(filepath,"a");
	if(pFile == NULL)
	{
		if(printFlag == 1)
		{
			cout<<"no file"<<endl;
		}
		return;
	}
	msg_header logHdr;
	fillMsgHdrStructure(logHdr,lbtemp.listBuff);
	char msgTypeForm[5];
	if(logHdr.msgtype==(uint8_t)HLLO)
	{
		memset(msgTypeForm,'\0',5);
		strcpy(msgTypeForm,"HLLO");	
	}
	else if(logHdr.msgtype==(uint8_t)STRQ)
	{
		memset(msgTypeForm,'\0',5);
		strcpy(msgTypeForm,"STRQ");
	}
	else if(logHdr.msgtype==(uint8_t)STRS)
	{
		memset(msgTypeForm,'\0',5);
		strcpy(msgTypeForm,"STRS");
	}
	else if(logHdr.msgtype==(uint8_t)KPAV)
	{
		memset(msgTypeForm,'\0',5);
		strcpy(msgTypeForm,"KPAV");
	}
	else if(logHdr.msgtype==(uint8_t)JNRQ)
	{
		memset(msgTypeForm,'\0',5);
		strcpy(msgTypeForm,"JNRQ");
	}
	else if(logHdr.msgtype==(uint8_t)JNRS)
	{
		memset(msgTypeForm,'\0',5);
		strcpy(msgTypeForm,"JNRS");
	}
	else if(logHdr.msgtype==(uint8_t)NTFY)
	{
		memset(msgTypeForm,'\0',5);
		strcpy(msgTypeForm,"NTFY");
	}
	else if(logHdr.msgtype==(uint8_t)CKRS)
	{
		memset(msgTypeForm,'\0',5);
		strcpy(msgTypeForm,"CKRS");
	}
	else if(logHdr.msgtype==(uint8_t)CKRQ)
	{
		memset(msgTypeForm,'\0',5);
		strcpy(msgTypeForm,"CKRQ");
	}
	
	if(logHdr.msgtype==(uint8_t)HLLO)
	{
		uint16_t portnumber;
		char localhostname[512];
		memset(localhostname,'\0',512);
		memcpy(&portnumber,&(lbtemp.listBuff[27]),sizeof(uint16_t));
		memcpy(localhostname,&(lbtemp.listBuff[29]),logHdr.datalength-sizeof(uint16_t));
		sprintf(data,"%d %s",portnumber,localhostname);
	}
	if(logHdr.msgtype==(uint8_t)STRQ)
	{
		uint8_t statustype;
		memcpy(&statustype,&(lbtemp.listBuff[27]),sizeof(uint8_t));
		sprintf(data,"0x%02x",statustype);
	}
	if(logHdr.msgtype==(uint8_t)STRS)
	{
		char lastuoid[4];
		memset(lastuoid,'\0',4);
		memcpy(lastuoid,&(lbtemp.listBuff[43]),4);
		sprintf(data,"%02x%02x%02x%02x",(uint8_t)(lastuoid[0]),(uint8_t)(lastuoid[1]),(uint8_t)(lastuoid[2]),(uint8_t)(lastuoid[3]));
	}
	if(logHdr.msgtype==(uint8_t)KPAV)
	{
	
	}
	if(logHdr.msgtype==(uint8_t)NTFY)
	{
		uint8_t type;
		memcpy(&type, &(lbtemp.listBuff[27]), sizeof(uint8_t));
		sprintf(data,"%d", type);
	}
	if(logHdr.msgtype==(uint8_t)JNRQ)
	{
		uint16_t portnumber;
		char localhostname[512];
		memset(localhostname,'\0',512);
		memcpy(&portnumber,&(lbtemp.listBuff[31]),sizeof(uint16_t));
		memcpy(localhostname,&(lbtemp.listBuff[33]),logHdr.datalength-sizeof(uint16_t)-sizeof(uint32_t));
		sprintf(data,"%d %s",portnumber,localhostname);
	}
	if(logHdr.msgtype==(uint8_t)JNRS)
	{
		uint16_t portnumber;
		uint32_t distance;
		char lastuoid[4];
		memset(lastuoid,'\0',4);
		memcpy(lastuoid,&(lbtemp.listBuff[43]),4);
		char localhostname[512];
		memset(localhostname,'\0',512);
		memcpy(&distance,&(lbtemp.listBuff[47]),sizeof(uint32_t));
		memcpy(&portnumber,&(lbtemp.listBuff[51]),sizeof(uint16_t));
		memcpy(localhostname,&(lbtemp.listBuff[53]),lbtemp.datalength-27-sizeof(uint16_t)-sizeof(uint32_t));
		sprintf(data,"%02x%02x%02x%02x %d %d %s",(uint8_t)(lastuoid[0]),(uint8_t)(lastuoid[1]),(uint8_t)(lastuoid[2]),(uint8_t)(lastuoid[3]),distance, portnumber,localhostname);
	}
	if(logHdr.msgtype==(uint8_t)CKRQ)
	{
	}
	if(logHdr.msgtype==(uint8_t)CKRS)
	{
		char lastuoid[4];
		memset(lastuoid,'\0',4);
		memcpy(lastuoid,&(lbtemp.listBuff[27]),4);
		sprintf(data,"%02x%02x%02x%02x",(uint8_t)(lastuoid[0]),(uint8_t)(lastuoid[1]),(uint8_t)(lastuoid[2]),(uint8_t)(lastuoid[3]));
	}
	if(logtype==0)
	{
		sprintf(message,"r %10ld.%03d %s_%d %s %d %d %02x%02x%02x%02x %s\n",exectime.tv_sec,(int)exectime.tv_usec/1000,hostname,lbtemp.portNumber,msgTypeForm,lbtemp.datalength,logHdr.timetolive,(uint8_t)(logHdr.uoid[16]),(uint8_t)(logHdr.uoid[17]),(uint8_t)(logHdr.uoid[18]),(uint8_t)(logHdr.uoid[19]),data);
		//fputs ("r <time> <from> <msgtype> <size> <ttl> <msgid> <data>\n",pFile);    
		fputs (message,pFile);   
	}
	else if(logtype==1)
	{
		sprintf(message,"s %10ld.%03d %s_%d %s %d %d %02x%02x%02x%02x %s\n",exectime.tv_sec,(int)exectime.tv_usec/1000,hostname,lbtemp.portNumber,msgTypeForm,lbtemp.datalength,logHdr.timetolive,(uint8_t)(logHdr.uoid[16]),(uint8_t)(logHdr.uoid[17]),(uint8_t)(logHdr.uoid[18]),(uint8_t)(logHdr.uoid[19]),data);
		//fputs ("s <time> <from> <msgtype> <size> <ttl> <msgid> <data>\n",pFile);
		fputs (message,pFile);
	}
	else if(logtype==2)
	{
		sprintf(message,"f %10ld.%03d %s_%d %s %d %d %02x%02x%02x%02x %s\n",exectime.tv_sec,(int)exectime.tv_usec/1000,hostname,lbtemp.portNumber,msgTypeForm,lbtemp.datalength,logHdr.timetolive,(uint8_t)(logHdr.uoid[16]),(uint8_t)(logHdr.uoid[17]),(uint8_t)(logHdr.uoid[18]),(uint8_t)(logHdr.uoid[19]),data);
		//fputs ("f <time> <from> <msgtype> <size> <ttl> <msgid> <data>\n",pFile);
		fputs (message,pFile);
	}
	fclose(pFile);
}

char *GetUOID(char *node_inst_id, char *obj_type, char *uoid_buf, int uoid_buf_sz)
{
	static unsigned long seq_no=(unsigned long)1;
	char sha1_buf[SHA_DIGEST_LENGTH], str_buf[104];
	snprintf(str_buf, sizeof(str_buf), "%s_%s_%1ld",node_inst_id, obj_type, (long)seq_no++);
	SHA1((const unsigned char*)str_buf, strlen(str_buf),(unsigned char*)sha1_buf);
	memset(uoid_buf, 0, uoid_buf_sz);
	memcpy(uoid_buf, sha1_buf,min((unsigned int)uoid_buf_sz,sizeof(sha1_buf)));
	return uoid_buf;
}

void createMsgHdrStr(msg_header *msg, uint8_t msgtype,uint8_t timetolive,char* obj,char* node_inst_id,uint32_t msgsize)
{
	char* str;
	char buf[SHA_DIGEST_LENGTH];

	str = GetUOID(node_inst_id,obj, buf, sizeof(buf));
	
	msg->msgtype = (uint8_t)msgtype;
	memcpy(msg->uoid,buf,20);
	msg->timetolive = timetolive;
	msg->reserved = 0;
	memcpy(&(msg->datalength), &msgsize, sizeof(uint32_t)); 
}
 
void remAllWhiteSpaces(char *str)
{
	unsigned int i = 0;
	char *ptr = str;
	for(i = 0; i < strlen(str) || *ptr == '\0'; )
	{
		if(*ptr == ' ')
		{
			memcpy(ptr, ptr + 1, strlen(ptr)+1);
			continue;
		}
		else
		{
			i++;
			ptr++;
		}
	}
}

void parserInitNeighFile()
{
	char file[256];
	char line[256];
	char *findColan;
	
	memset(file, '\0', 256);
	strcpy(file, initMap["HomeDir"].asStr);
	strcat(file, "/init_neighbor_list");
	FILE* fp = fopen(file,"rb");
	if(fp == NULL)
	{
		if(printFlag==1)
		{
			cout<<"File does not exist"<<endl;
		}
		exit(1);
	}
	while(fgets(line,sizeof(line),fp))
	{
		findColan = strchr(line, ':');
		if(findColan != NULL)
		{
			beacon bec;
			char *value;
			value = strtok(line, ":");
			strcpy(bec.hostName, value);
			while(value != NULL)
			{
				value = strtok(NULL, ":");
				if(value != NULL)
				{
					bec.port = atoi(value);
				}
			}
			allNeighbors.push_back(bec);
		}
	}
}

void parser_function(int argc, char *argv[])
{
	char stringBuf[100];
	char empty_string[100];
	memset(&stringBuf, '\0', sizeof("servant.log"));
	strcpy(stringBuf, "servant.log");
	strcpy(empty_string, "");
	initMap["Port"] = (double)-1;
	initMap["Location"] = (double)-1;
	initMap["HomeDir"] = empty_string;
	initMap["LogFilename"] = stringBuf;
	initMap["AutoShutdown"] = (double)900;
	initMap["TTL"] = (double)30;
	initMap["MsgLifetime"] = (double)60;
	initMap["GetMsgLifetime"] = (double)300;
	initMap["InitNeighbors"] = (double)3;
	initMap["JoinTimeout"] = (double)15;
	initMap["KeepAliveTimeout"] = (double)60;
	initMap["MinNeighbors"] = (double)2;
	initMap["CacheProb"] = (double)0.1;
	initMap["StoreProb"] = (double)0.1;
	initMap["NeighborStoreProb"] = (double)0.2;
	initMap["CacheSize"] = (double)500;
	initMap["NoCheck"] = (double)0;
	initMap["Retry"] = (double)30;
	
	int checkBeaconPort;
	char *beaconHostName;
	
	char *file;
	char line[255];
	char *findEquals;
	char *findColan;
	char *findSemicolan;
	char *keyValue;
	char key[100];
	string KEY;
	
	argc--, argv++;
	for (; argc > 0; argc--, argv++) 
	{
		if (*argv[0] == '-') 
		{
			if (strcmp(*argv, "-reset") == 0) 
			{
				resetFlag = true;
			}
		}
		else
		{
			//store the input file name
			file = *argv;
			FILE* fp = fopen(file,"rb");
			if(fp == NULL)
			{
				if(printFlag==1)
				{
					cout<<"File does not exist"<<endl;
				}
				exit(1);
			}
			while(fgets(line,sizeof(line),fp))
			{
				//First remove the whitespaces in the line
				remAllWhiteSpaces(line);
				//Find '=' which says its a key value pair
				findEquals = strchr(line, '=');
				if(findEquals == NULL)
				{
					continue;
				}
				else
				{
					//Find ';' then its a comment so ignore even if '=' exists
					findSemicolan = strchr(line, ';');
					if(findSemicolan == NULL)
					{
						//find for ':' signifies beacons else init variables
						findColan = strchr(line, ':');
						if(findColan == NULL)
						{
							keyValue = strtok(line, "=");
							memset(&key, '\0', sizeof(keyValue));
							strcpy(key, keyValue);
							KEY.assign(key);
							while(keyValue != NULL)
							{
								keyValue = strtok(NULL,"=");
								if(keyValue != NULL)
								{
									keyValue[strlen(keyValue)-1] = '\0';
									if(KEY.compare("HomeDir") == 0||KEY.compare("LogFilename") == 0)
									{
										char homeBuf[100];
										memset(&homeBuf, '\0', sizeof(keyValue));
										strcpy(homeBuf, keyValue);
										initMap[KEY] = homeBuf;
									}
									else
									{
										initMap[KEY] = atof(keyValue);
										
									}
								}
							}
						}
						else
						{
							beacon bec;
							beaconHostName = strtok(line, "=");
							if(beaconHostName != NULL)
							{
								keyValue = strtok(beaconHostName, ":");
								strcpy(bec.hostName, keyValue);
								while(keyValue != NULL)
								{
									keyValue = strtok(NULL, ":");
									if(keyValue != NULL)
									{
										checkBeaconPort = atoi(keyValue);
										bec.port = checkBeaconPort;
										if(initMap["Port"].asDouble == checkBeaconPort)
										{
											isBeacon = true;
										}
									}
								}
							}
							allNeighbors.push_back(bec);
						}
					}
					else
					{
						continue;
					}
				}
			}
			fclose(fp);
		}
	}
}

void fillMsgHdrStructure(msg_header &hdr, char* buf)
{
	memcpy(&hdr.msgtype, &buf[0], sizeof(uint8_t));
	memcpy(&hdr.uoid, &buf[1], 20);
	memcpy(&hdr.timetolive, &buf[21], sizeof(uint8_t));
	memcpy(&hdr.reserved, &buf[22], sizeof(uint8_t));
	memcpy(&hdr.datalength, &buf[23], sizeof(uint32_t));
}

void fillMsgHdrBuffer(msg_header &hdr, char* buf)
{
	memcpy(&buf[0], &hdr.msgtype, sizeof(uint8_t));
	memcpy(&buf[1], &hdr.uoid,  20);
	memcpy(&buf[21], &hdr.timetolive, sizeof(uint8_t));
	memcpy(&buf[22], &hdr.reserved, sizeof(uint8_t));
	memcpy(&buf[23], &hdr.datalength, sizeof(uint32_t));
}
